package utils

import (
	"bytes"
	"compress/gzip"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

// HTTP客户端
var httpClient *http.Client         // 超时时间5分钟
var uploadHttpClient *http.Client   // 超时时间60分钟
var downloadHttpClient *http.Client // 超时时间120分钟

type HttpResult struct {
	Status int         `json:"status"`
	Header http.Header `json:"header"`
	Body   []byte      `json:"body"`
}

type UploadFile struct {
	Name       string
	FilePath   string                // 本地文件路径
	FileHeader *multipart.FileHeader // http上传的文件
}

func GetHttpClient() *http.Client {
	if httpClient == nil {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		httpClient = &http.Client{Transport: tr, Timeout: 15 * time.Minute}
	}
	return httpClient
}

func GetUploadHttpClient() *http.Client {
	if uploadHttpClient == nil {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		uploadHttpClient = &http.Client{Transport: tr, Timeout: 60 * time.Minute}
	}
	return uploadHttpClient
}

func GetDownloadHttpClient() *http.Client {
	if downloadHttpClient == nil {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		downloadHttpClient = &http.Client{Transport: tr, Timeout: 120 * time.Minute}
	}
	return downloadHttpClient
}

func GetProxyHttpClient(proxyServerUrl string) *http.Client {
	if httpClient == nil {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		// 设置代理
		proxyUrl, err := url.Parse(proxyServerUrl)
		if err != nil {
			slog.Error(err.Error())
			tr.Proxy = http.ProxyFromEnvironment // 使用系统代理
		} else {
			tr.Proxy = http.ProxyURL(proxyUrl) // 使用配置代理
		}

		httpClient = &http.Client{Transport: tr, Timeout: 15 * time.Minute}
	}
	return httpClient
}

func doHttpRequest(request *http.Request, proxyServerUrl string) (*HttpResult, error) {
	var client *http.Client

	if len(proxyServerUrl) > 0 {
		client = GetProxyHttpClient(proxyServerUrl)
	} else {
		if request.Header != nil {
			if strings.HasPrefix(request.Header.Get("Content-Type"), "multipart/form-data") {
				client = GetUploadHttpClient()
			} else if request.Header.Get("X-Download") == "1" {
				request.Header.Del("X-Download")
				client = GetDownloadHttpClient()
			} else {
				client = GetHttpClient()
			}
		} else {
			client = GetHttpClient()
		}
	}

	resp, err := client.Do(request)
	if err != nil {
		slog.Error(err.Error(), "path", request.URL.Path, "query", request.URL.RawQuery)
		return nil, err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			slog.Error(err.Error())
		}
	}(resp.Body)

	// 返回数据
	respHeader := resp.Header.Clone()
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}

	// 兼容gzip
	if respHeader.Get("Content-Encoding") == "gzip" {
		respBodyDecoded, err := decodeGzipData(respBody)
		if err != nil {
			slog.Error(err.Error())
			return nil, err
		}
		respBody = respBodyDecoded
		respHeader.Del("Content-Encoding")
		respHeader.Set("Content-Length", fmt.Sprintf("%d", len(respBody)))
	}

	var httpResult = HttpResult{
		Status: resp.StatusCode,
		Header: respHeader,
		Body:   respBody,
	}
	// 200~300之间，都算返回正常结果
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return &httpResult, nil
	} else {
		return &httpResult, errors.New(strconv.Itoa(resp.StatusCode) + ", " + resp.Status)
	}
}

func doHttpGet(requestUrl string, headers map[string]string, proxyServerUrl string) (*HttpResult, error) {
	request, err := http.NewRequest("GET", requestUrl, nil)
	if err != nil {
		return nil, err
	}
	if headers == nil || headers["Content-Type"] == "" {
		request.Header.Set("Content-Type", "application/json")
	}
	if headers != nil {
		for key, value := range headers {
			request.Header.Set(key, value)
		}
	}
	return doHttpRequest(request, proxyServerUrl)
}

func doHttpPostForm(requestUrl string, headers map[string]string, params map[string]string, proxyServerUrl string) (*HttpResult, error) {
	var values = url.Values{}
	if params != nil {
		for key, value := range params {
			values.Add(key, value)
		}
	}

	request, err := http.NewRequest("POST", requestUrl, strings.NewReader(values.Encode()))
	if err != nil {
		return nil, err
	}

	// form表单
	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	if headers != nil {
		for key, value := range headers {
			request.Header.Set(key, value)
		}
	}

	return doHttpRequest(request, proxyServerUrl)
}

func doHttpPostMultipartForm(requestUrl string, headers map[string]string,
	textParams map[string]string,
	fileParams map[string]UploadFile,
	proxyServerUrl string) (*HttpResult, error) {
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)

	// 文件放入body
	for field, uploadFile := range fileParams {
		formFile, err := writer.CreateFormFile(field, uploadFile.Name)
		if err != nil {
			return nil, err
		}
		if err := copyFileToWriter(&formFile, uploadFile); err != nil {
			return nil, err
		}
	}

	// 其他参数放入body
	for key, val := range textParams {
		_ = writer.WriteField(key, val)
	}
	if err := writer.Close(); err != nil {
		return nil, err
	}

	request, err := http.NewRequest("POST", requestUrl, body)
	if err != nil {
		return nil, err
	}

	// form表单
	request.Header.Add("Content-Type", writer.FormDataContentType())

	if headers != nil {
		for key, value := range headers {
			request.Header.Set(key, value)
		}
	}

	return doHttpRequest(request, proxyServerUrl)
}

func doHttpPost(requestUrl string, headers map[string]string, body []byte, proxyServerUrl string) (*HttpResult, error) {
	request, err := http.NewRequest("POST", requestUrl, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	if headers == nil || headers["Content-Type"] == "" {
		request.Header.Set("Content-Type", "application/json")
	}
	if headers != nil {
		for key, value := range headers {
			request.Header.Set(key, value)
		}
	}

	return doHttpRequest(request, proxyServerUrl)
}

func doHttpPut(requestUrl string, headers map[string]string, body []byte, proxyServerUrl string) (*HttpResult, error) {
	request, err := http.NewRequest("PUT", requestUrl, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	if headers == nil || headers["Content-Type"] == "" {
		request.Header.Set("Content-Type", "application/json")
	}
	if headers != nil {
		for key, value := range headers {
			request.Header.Set(key, value)
		}
	}

	return doHttpRequest(request, proxyServerUrl)
}

func doHttpPatch(requestUrl string, headers map[string]string, body []byte, proxyServerUrl string) (*HttpResult, error) {
	request, err := http.NewRequest("PATCH", requestUrl, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	if headers == nil || headers["Content-Type"] == "" {
		request.Header.Set("Content-Type", "application/json")
	}
	if headers != nil {
		for key, value := range headers {
			request.Header.Set(key, value)
		}
	}

	return doHttpRequest(request, proxyServerUrl)
}

func doHttpDelete(requestUrl string, headers map[string]string, proxyServerUrl string) (*HttpResult, error) {
	request, err := http.NewRequest("DELETE", requestUrl, bytes.NewBuffer(make([]byte, 0, 0)))
	if err != nil {
		return nil, err
	}
	if headers == nil || headers["Content-Type"] == "" {
		request.Header.Set("Content-Type", "application/json")
	}
	if headers != nil {
		for key, value := range headers {
			request.Header.Set(key, value)
		}
	}

	return doHttpRequest(request, proxyServerUrl)
}

func HttpGet(requestUrl string, headers map[string]string) (*HttpResult, error) {
	return doHttpGet(requestUrl, headers, "")
}

func HttpPostForm(requestUrl string, headers map[string]string, params map[string]string) (*HttpResult, error) {
	return doHttpPostForm(requestUrl, headers, params, "")
}

func HttpPostMultipartForm(requestUrl string,
	headers map[string]string,
	textParams map[string]string,
	fileParams map[string]string) (*HttpResult, error) {
	var uploadFileParams = map[string]UploadFile{}
	for field, filePath := range fileParams {
		var fileName = filePath[strings.LastIndex(filePath, "/")+1:]
		uploadFileParams[field] = UploadFile{
			Name:       fileName,
			FilePath:   filePath,
			FileHeader: nil,
		}
	}
	return doHttpPostMultipartForm(requestUrl, headers, textParams, uploadFileParams, "")
}

func HttpPostMultipartForm2(requestUrl string,
	headers map[string]string,
	textParams map[string]string,
	fileParams map[string]UploadFile) (*HttpResult, error) {
	return doHttpPostMultipartForm(requestUrl, headers, textParams, fileParams, "")
}

func HttpPost(requestUrl string, headers map[string]string, body []byte) (*HttpResult, error) {
	return doHttpPost(requestUrl, headers, body, "")
}

func HttpPut(requestUrl string, headers map[string]string, body []byte) (*HttpResult, error) {
	return doHttpPut(requestUrl, headers, body, "")
}

func HttpPatch(requestUrl string, headers map[string]string, body []byte) (*HttpResult, error) {
	return doHttpPatch(requestUrl, headers, body, "")
}

func HttpDelete(requestUrl string, headers map[string]string) (*HttpResult, error) {
	return doHttpDelete(requestUrl, headers, "")
}

func copyFileToWriter(formFile *io.Writer, uploadFile UploadFile) error {
	if len(uploadFile.FilePath) > 0 {
		file, err := os.Open(uploadFile.FilePath)
		if err != nil {
			return err
		}
		defer func(file *os.File) {
			err := file.Close()
			if err != nil {
				slog.Error(err.Error())
			}
		}(file)
		if _, err := io.Copy(*formFile, file); err != nil {
			return err
		}
	} else if uploadFile.FileHeader != nil {
		file, err := uploadFile.FileHeader.Open()
		if err != nil {
			return err
		}
		defer func(file multipart.File) {
			err := file.Close()
			if err != nil {
				slog.Error(err.Error())
			}
		}(file)
		if _, err := io.Copy(*formFile, file); err != nil {
			return err
		}
	}
	return nil
}

func decodeGzipData(data []byte) ([]byte, error) {
	reader, err := gzip.NewReader(bytes.NewReader(data))
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}
	defer func(reader *gzip.Reader) {
		err := reader.Close()
		if err != nil {
			slog.Error(err.Error())
		}
	}(reader)
	var respBodyDecoded []byte
	respBodyDecoded, err = io.ReadAll(reader)
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}
	return respBodyDecoded, nil
}
